Here we will map an example dataset from Roy et al (Cell Rep 2019)
comprising 12,245 CD34+ cells from fetal and adult bone marrow. Note
that any human hematopoietic cells, regardless of tissue source or
disease status, can be mapped to this reference. This tutorial will take
approximately 10 minutes to run
Setup
library(Seurat)
library(tidyverse)
library(symphony)
library(ggpubr)
library(patchwork)
Install package from github
devtools::install_github('andygxzeng/BoneMarrowMap', force = TRUE)
library(BoneMarrowMap)
Download reference object and UMAP model
Set projection folder, Download reference object and UMAP model
# Set directory to store projection reference files
projection_path = './'
# Download Bone Marrow Reference - 344 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_SymphonyRef.rds',
destfile = paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Download uwot model file - 221 Mb
curl::curl_download('https://bonemarrowmap.s3.us-east-2.amazonaws.com/BoneMarrow_RefMap_uwot_model.uwot',
destfile = paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot'))
# Load Symphony reference
ref <- readRDS(paste0(projection_path, 'BoneMarrow_RefMap_SymphonyRef.rds'))
# Set uwot path for UMAP projection
ref$save_uwot_path <- paste0(projection_path, 'BoneMarrow_RefMap_uwot_model.uwot')
Visualize Bone Marrow Reference
If we want to visualize celltype labels or metadata from the BM
Reference, we can create a Seurat Object from the symphony reference
This will be memory efficient as it will not include gene expression
counts, only the UMAP coordinates and the metadata including cell labels
and sorting information

We can visualize other annotations too, including cell cycle phase
and lineage pseudotime estimates.
p1 <- DimPlot(ReferenceSeuratObj, reduction = 'umap', group.by = 'CyclePhase', raster=FALSE)
p2 <- FeaturePlot(ReferenceSeuratObj, reduction = 'umap', features = 'Pseudotime', raster=FALSE)
p1 + p2

Load Query Seurat object
Example Query object is a subset of data from Roy et al (Cell Reports
2021), incorporating CD34p cells from an adult bone marrow sample and a
fetal bone marrow sample. Here, we prefer raw count data without
normalization.
Input does not need to be a seurat object, can load raw count matrix
and metadata separately
# Load seurat object
query <- readRDS(paste0(projection_path, 'ExampleQuery_Roy2021.rds'))
query
An object of class Seurat
33538 features across 12245 samples within 1 assay
Active assay: RNA (33538 features, 0 variable features)
Map the Query Data
Provide raw counts, metadata, and donor key. This should take <1
min Calculate mapping error and perform QC to remove low quality cells
with high mapping error
# batch variable to correct in the query data, set as NULL if no batches in query
batchvar <- 'sampleID'
# Map query dataset using Symphony (Kang et al 2021)
query <- map_Query(
exp_query = query@assays$RNA@counts,
metadata_query = query@meta.data,
ref_obj = ref,
vars = batchvar
)
Normalizing
Scaling and synchronizing query gene expression
Found 2386 reference variable genes in query dataset
Project query cells using reference gene loadings
Clustering query cells to reference centroids
Correcting query batch effects
UMAP
All done!
Warning: Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim.RNA.harmony; see ?make.names for more details on syntax validity
Now that the data is mapped, we will evaluate the mapping QC metrics
and flag cells with high mapping errors
# Run QC based on mapping error score, flag cells with mapping error >= 2.5 MADs above median
query <- query %>% calculate_MappingError(., reference = ref, MAD.threshold = 2.5)
# Get QC Plots
QC_plots <- plot_MappingErrorQC(query)
# Plot together - If this is too crowded, can also just call "QC_plots" to display one by one
patchwork::wrap_plots(QC_plots, ncol = 4, widths = c(0.8, 0.3, 0.8, 0.3))

This important step identifies a subset of cells with high mapping
error from the query dataset that are either: 1) not present within the
reference, or 2) have poor QC metrics (low RNA counts and low
transcriptional diversity)
Sometimes, low quality cells may erroneously map to the
orthochromatic erythroblast region as this cell type has very low
transcriptional diversity. These low quality query cells do not have
hemoglobin expression and are in fact mis-mapped; they will be flagged
by the QC filter and excluded from cell type assignments.
Please adjust the MAD.threshold (typically between 1 and 3)
based on the distribution of your dataset to identify the outliers with
low quality and high mapping error scores. This will improve your
classifications and any downstream composition analysis
# # Optional step - remove outliers with high mapping error
# query <- subset(query, mapping_error_QC == 'Pass')
Optionally, outlier cells with high mapping error can also be removed
at this stage. For ease of integrating these mapped annotations with the
rest of your analysis, we also choose to skip this step. If so, Final
CellType and Pseudotime predictions will be assigned as NA for cells
failing the mapping error QC threshold.
Cell Type Assignments
We will next use a KNN classifier to assign cell identity based on
the 30 K-Nearest Neighbours from the reference map. This label transfer
step will take longer, potentially around 10 minutes for ~10,000
cells
# Predict Hematopoietic Cell Types by KNN classification
query <- predict_CellTypes(
query_obj = query,
ref_obj = ref,
initial_label = 'initial_CellType', # celltype assignments before mapping QC
final_label = 'predicted_CellType' # celltype assignments with map QC failing cells assigned as NA
)
DimPlot(subset(query, mapping_error_QC == 'Pass'), reduction = 'umap', group.by = c('predicted_CellType'), raster=FALSE, label=TRUE, label.size = 4)

Pseudotime Annotations
We can also annotate each query cell based on their position along
hematopoietic pseudotime. Query cells will be assigned a pseudotime
score based on the 30 K-Nearest Neighbours from the reference map. Since
our Pseudotime KNN assignments are performed in UMAP space (more
accurate than KNN on harmony components), this step is very fast (<
10s)
# Predict Pseudotime values by KNN
query <- predict_Pseudotime(
query_obj = query,
ref_obj = ref,
initial_label = 'initial_Pseudotime',
final_label = 'predicted_Pseudotime'
)
# Visualize Hematopoietic Pseudotime in query data
FeaturePlot(subset(query, mapping_error_QC == 'Pass'), features = c('predicted_Pseudotime'))

Save projection results
This will save a csv file with the mapped annotations for each cell
(mapping error scores, umap coordinates, predicted Cell Type, and
predicted Pseudotime)
# Save CellType Annotations and Projected UMAP coordinates
save_ProjectionResults(
query_obj = query,
celltype_label = 'predicted_CellType',
celltype_KNNprob_label = 'predicted_CellType_prob',
pseudotime_label = 'predicted_Pseudotime',
file_name = 'querydata_projected_labeled.csv')
Visualize Projection Density
Now let’s visualize the density distribution of query cells across
the hematopoietic hierarchy

We can also set Hierarchy_only = TRUE to remove T/NK/Plasma/Stromal
cells and focus solely on the hematopoietic hierarchy.

Note how cell composition differs between CD34p sorted Adult Bone
Marrow and Fetal Bone Marrow within the Roy 2021 dataset. Notably, Adult
bone marrow is enriched for more early HSC/MPP and LMPP cells and also
MEP & Early Erythroid lineages. In contrast, Fetal bone marrow is
highly enriched for MLP and B cell progenitors.
We can also compare the mapping of these samples along Hematopoietic
pseudotime:
Get Composition data for each donor
Here, to study the abundance of each cell type within each donor, I
focus on cells that were classified with a KNN prob > 0.5 (that is,
>50% of nearest neighbours from the reference map agree on the
assigned cell type).
We can present this as a long table
query_composition <- get_Composition(
query_obj = query,
donor_key = 'sampleID',
celltype_label = 'predicted_CellType',
mapQC_col = 'mapping_error_QC',
knn_prob_cutoff = NULL,
return_type = 'long')
query_composition
Or as a wide table with counts of # of cells
query_composition <- get_Composition(
query_obj = query,
donor_key = 'sampleID',
celltype_label = 'predicted_CellType',
mapQC_col = 'mapping_error_QC',
knn_prob_cutoff = NULL,
return_type = 'count')
query_composition
Or as a wide table with proportion of each cell type within each
donor
query_composition <- get_Composition(
query_obj = query,
donor_key = 'sampleID',
celltype_label = 'predicted_CellType',
mapQC_col = 'mapping_error_QC',
knn_prob_cutoff = NULL,
return_type = 'proportion')
query_composition
# Simple heatmap to visualize composition of projected samples
p <- query_composition %>% column_to_rownames('sampleID') %>% data.matrix() %>% ComplexHeatmap::Heatmap()
p

LS0tCnRpdGxlOiAiQm9uZSBNYXJyb3cgUmVmZXJlbmNlIE1hcCBQcm9qZWN0aW9uIFR1dG9yaWFsIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpIZXJlIHdlIHdpbGwgbWFwIGFuIGV4YW1wbGUgZGF0YXNldCBmcm9tIFJveSBldCBhbCAoQ2VsbCBSZXAgMjAxOSkgY29tcHJpc2luZyAxMiwyNDUgQ0QzNCsgY2VsbHMgZnJvbSBmZXRhbCBhbmQgYWR1bHQgYm9uZSBtYXJyb3cuCk5vdGUgdGhhdCBhbnkgaHVtYW4gaGVtYXRvcG9pZXRpYyBjZWxscywgcmVnYXJkbGVzcyBvZiB0aXNzdWUgc291cmNlIG9yIGRpc2Vhc2Ugc3RhdHVzLCBjYW4gYmUgbWFwcGVkIHRvIHRoaXMgcmVmZXJlbmNlLgpUaGlzIHR1dG9yaWFsIHdpbGwgdGFrZSBhcHByb3hpbWF0ZWx5IDEwIG1pbnV0ZXMgdG8gcnVuCgojIyMgU2V0dXAKCmBgYHtyfQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3ltcGhvbnkpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KHBhdGNod29yaykKYGBgCgpJbnN0YWxsIHBhY2thZ2UgZnJvbSBnaXRodWIKCmBgYHtyfQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ2FuZHlneHplbmcvQm9uZU1hcnJvd01hcCcsIGZvcmNlID0gVFJVRSkKbGlicmFyeShCb25lTWFycm93TWFwKQpgYGAKCgojIyMjIERvd25sb2FkIHJlZmVyZW5jZSBvYmplY3QgYW5kIFVNQVAgbW9kZWwKClNldCBwcm9qZWN0aW9uIGZvbGRlciwgRG93bmxvYWQgcmVmZXJlbmNlIG9iamVjdCBhbmQgVU1BUCBtb2RlbAoKYGBge3J9CiMgU2V0IGRpcmVjdG9yeSB0byBzdG9yZSBwcm9qZWN0aW9uIHJlZmVyZW5jZSBmaWxlcwpwcm9qZWN0aW9uX3BhdGggPSAnLi8nCgojIERvd25sb2FkIEJvbmUgTWFycm93IFJlZmVyZW5jZSAtIDM0NCBNYgpjdXJsOjpjdXJsX2Rvd25sb2FkKCdodHRwczovL2JvbmVtYXJyb3dtYXAuczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vQm9uZU1hcnJvd19SZWZNYXBfU3ltcGhvbnlSZWYucmRzJywgCiAgICAgICAgICAgICAgICAgICAgZGVzdGZpbGUgPSBwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnQm9uZU1hcnJvd19SZWZNYXBfU3ltcGhvbnlSZWYucmRzJykpCiMgRG93bmxvYWQgdXdvdCBtb2RlbCBmaWxlIC0gMjIxIE1iCmN1cmw6OmN1cmxfZG93bmxvYWQoJ2h0dHBzOi8vYm9uZW1hcnJvd21hcC5zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9Cb25lTWFycm93X1JlZk1hcF91d290X21vZGVsLnV3b3QnLCAKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdCb25lTWFycm93X1JlZk1hcF91d290X21vZGVsLnV3b3QnKSkKCiMgTG9hZCBTeW1waG9ueSByZWZlcmVuY2UKcmVmIDwtIHJlYWRSRFMocGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JvbmVNYXJyb3dfUmVmTWFwX1N5bXBob255UmVmLnJkcycpKQojIFNldCB1d290IHBhdGggZm9yIFVNQVAgcHJvamVjdGlvbgpyZWYkc2F2ZV91d290X3BhdGggPC0gcGFzdGUwKHByb2plY3Rpb25fcGF0aCwgJ0JvbmVNYXJyb3dfUmVmTWFwX3V3b3RfbW9kZWwudXdvdCcpCmBgYAoKCiMjIyMgVmlzdWFsaXplIEJvbmUgTWFycm93IFJlZmVyZW5jZQoKSWYgd2Ugd2FudCB0byB2aXN1YWxpemUgY2VsbHR5cGUgbGFiZWxzIG9yIG1ldGFkYXRhIGZyb20gdGhlIEJNIFJlZmVyZW5jZSwgd2UgY2FuIGNyZWF0ZSBhIFNldXJhdCBPYmplY3QgZnJvbSB0aGUgc3ltcGhvbnkgcmVmZXJlbmNlIApUaGlzIHdpbGwgYmUgbWVtb3J5IGVmZmljaWVudCBhcyBpdCB3aWxsIG5vdCBpbmNsdWRlIGdlbmUgZXhwcmVzc2lvbiBjb3VudHMsIG9ubHkgdGhlIFVNQVAgY29vcmRpbmF0ZXMgYW5kIHRoZSBtZXRhZGF0YSBpbmNsdWRpbmcgY2VsbCBsYWJlbHMgYW5kIHNvcnRpbmcgaW5mb3JtYXRpb24KCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMX0KUmVmZXJlbmNlU2V1cmF0T2JqIDwtIGNyZWF0ZV9SZWZlcmVuY2VPYmplY3QocmVmKQoKRGltUGxvdChSZWZlcmVuY2VTZXVyYXRPYmosIHJlZHVjdGlvbiA9ICd1bWFwJywgZ3JvdXAuYnkgPSAnQ2VsbFR5cGVfQW5ub3RhdGlvbl9mb3JtYXR0ZWQnLCAKICAgICAgICByYXN0ZXI9RkFMU0UsIGxhYmVsPVRSVUUsIGxhYmVsLnNpemUgPSA0KQpgYGAKCldlIGNhbiB2aXN1YWxpemUgb3RoZXIgYW5ub3RhdGlvbnMgdG9vLCBpbmNsdWRpbmcgY2VsbCBjeWNsZSBwaGFzZSBhbmQgbGluZWFnZSBwc2V1ZG90aW1lIGVzdGltYXRlcy4KCmBgYHtyLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTExfQpwMSA8LSBEaW1QbG90KFJlZmVyZW5jZVNldXJhdE9iaiwgcmVkdWN0aW9uID0gJ3VtYXAnLCBncm91cC5ieSA9ICdDeWNsZVBoYXNlJywgcmFzdGVyPUZBTFNFKQpwMiA8LSBGZWF0dXJlUGxvdChSZWZlcmVuY2VTZXVyYXRPYmosIHJlZHVjdGlvbiA9ICd1bWFwJywgZmVhdHVyZXMgPSAnUHNldWRvdGltZScsIHJhc3Rlcj1GQUxTRSkgCgpwMSArIHAyCmBgYAoKCiMjIyMgTG9hZCBRdWVyeSBTZXVyYXQgb2JqZWN0CkV4YW1wbGUgUXVlcnkgb2JqZWN0IGlzIGEgc3Vic2V0IG9mIGRhdGEgZnJvbSBSb3kgZXQgYWwgKENlbGwgUmVwb3J0cyAyMDIxKSwgaW5jb3Jwb3JhdGluZyBDRDM0cCBjZWxscyBmcm9tIGFuIGFkdWx0IGJvbmUgbWFycm93IHNhbXBsZSBhbmQgYSBmZXRhbCBib25lIG1hcnJvdyBzYW1wbGUuIEhlcmUsIHdlIHByZWZlciByYXcgY291bnQgZGF0YSB3aXRob3V0IG5vcm1hbGl6YXRpb24uCgpJbnB1dCBkb2VzIG5vdCBuZWVkIHRvIGJlIGEgc2V1cmF0IG9iamVjdCwgY2FuIGxvYWQgcmF3IGNvdW50IG1hdHJpeCBhbmQgbWV0YWRhdGEgc2VwYXJhdGVseQoKYGBge3J9CiMgTG9hZCBleGFtcGxlIGRhdGEgZnJvbSBSb3kgZXQgYWwgLSAxNDEgTWIKY3VybDo6Y3VybF9kb3dubG9hZCgnaHR0cHM6Ly9ib25lbWFycm93bWFwLnMzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL0V4YW1wbGVRdWVyeV9Sb3kyMDIxLnJkcycsCiAgICAgICAgICAgICAgICAgICAgZGVzdGZpbGUgPSBwYXN0ZTAocHJvamVjdGlvbl9wYXRoLCAnRXhhbXBsZVF1ZXJ5X1JveTIwMjEucmRzJykpCgojIExvYWQgc2V1cmF0IG9iamVjdApxdWVyeSA8LSByZWFkUkRTKHBhc3RlMChwcm9qZWN0aW9uX3BhdGgsICdFeGFtcGxlUXVlcnlfUm95MjAyMS5yZHMnKSkKcXVlcnkKYGBgCgojIyMgTWFwIHRoZSBRdWVyeSBEYXRhClByb3ZpZGUgcmF3IGNvdW50cywgbWV0YWRhdGEsIGFuZCBkb25vciBrZXkuIFRoaXMgc2hvdWxkIHRha2UgPDEgbWluCkNhbGN1bGF0ZSBtYXBwaW5nIGVycm9yIGFuZCBwZXJmb3JtIFFDIHRvIHJlbW92ZSBsb3cgcXVhbGl0eSBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcgoKYGBge3J9CiMgYmF0Y2ggdmFyaWFibGUgdG8gY29ycmVjdCBpbiB0aGUgcXVlcnkgZGF0YSwgc2V0IGFzIE5VTEwgaWYgbm8gYmF0Y2hlcyBpbiBxdWVyeQpiYXRjaHZhciA8LSAnc2FtcGxlSUQnCgojIE1hcCBxdWVyeSBkYXRhc2V0IHVzaW5nIFN5bXBob255IChLYW5nIGV0IGFsIDIwMjEpCnF1ZXJ5IDwtIG1hcF9RdWVyeSgKICAgIGV4cF9xdWVyeSA9IHF1ZXJ5QGFzc2F5cyRSTkFAY291bnRzLCAKICAgIG1ldGFkYXRhX3F1ZXJ5ID0gcXVlcnlAbWV0YS5kYXRhLAogICAgcmVmX29iaiA9IHJlZiwKICAgIHZhcnMgPSBiYXRjaHZhcgopCmBgYAoKTm93IHRoYXQgdGhlIGRhdGEgaXMgbWFwcGVkLCB3ZSB3aWxsIGV2YWx1YXRlIHRoZSBtYXBwaW5nIFFDIG1ldHJpY3MgYW5kIGZsYWcgY2VsbHMgd2l0aCBoaWdoIG1hcHBpbmcgZXJyb3JzCgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9MTB9CiMgUnVuIFFDIGJhc2VkIG9uIG1hcHBpbmcgZXJyb3Igc2NvcmUsIGZsYWcgY2VsbHMgd2l0aCBtYXBwaW5nIGVycm9yID49IDIuNSBNQURzIGFib3ZlIG1lZGlhbgpxdWVyeSA8LSBxdWVyeSAlPiUgY2FsY3VsYXRlX01hcHBpbmdFcnJvciguLCByZWZlcmVuY2UgPSByZWYsIE1BRC50aHJlc2hvbGQgPSAyLjUpIAoKIyBHZXQgUUMgUGxvdHMKUUNfcGxvdHMgPC0gcGxvdF9NYXBwaW5nRXJyb3JRQyhxdWVyeSkKCiMgUGxvdCB0b2dldGhlciAtIElmIHRoaXMgaXMgdG9vIGNyb3dkZWQsIGNhbiBhbHNvIGp1c3QgY2FsbCAiUUNfcGxvdHMiIGFsb25ldG8gZGlzcGxheSBvbmUgYnkgb25lCnBhdGNod29yazo6d3JhcF9wbG90cyhRQ19wbG90cywgbmNvbCA9IDQsIHdpZHRocyA9IGMoMC44LCAwLjMsIDAuOCwgMC4zKSkKYGBgCgoKVGhpcyBpbXBvcnRhbnQgc3RlcCBpZGVudGlmaWVzIGEgc3Vic2V0IG9mIGNlbGxzIHdpdGggaGlnaCBtYXBwaW5nIGVycm9yIGZyb20gdGhlIHF1ZXJ5IGRhdGFzZXQgdGhhdCBhcmUgZWl0aGVyOgogIDEpIG5vdCBwcmVzZW50IHdpdGhpbiB0aGUgcmVmZXJlbmNlLCBvcgogIDIpIGhhdmUgcG9vciBRQyBtZXRyaWNzIChsb3cgUk5BIGNvdW50cyBhbmQgbG93IHRyYW5zY3JpcHRpb25hbCBkaXZlcnNpdHkpCgpTb21ldGltZXMsIGxvdyBxdWFsaXR5IGNlbGxzIG1heSBlcnJvbmVvdXNseSBtYXAgdG8gdGhlIG9ydGhvY2hyb21hdGljIGVyeXRocm9ibGFzdCByZWdpb24gYXMgdGhpcyBjZWxsIHR5cGUgaGFzIHZlcnkgbG93IHRyYW5zY3JpcHRpb25hbCBkaXZlcnNpdHkuIApUaGVzZSBsb3cgcXVhbGl0eSBxdWVyeSBjZWxscyBkbyBub3QgaGF2ZSBoZW1vZ2xvYmluIGV4cHJlc3Npb24gYW5kIGFyZSBpbiBmYWN0IG1pcy1tYXBwZWQ7IHRoZXkgd2lsbCBiZSBmbGFnZ2VkIGJ5IHRoZSBRQyBmaWx0ZXIgYW5kIGV4Y2x1ZGVkIGZyb20gY2VsbCB0eXBlIGFzc2lnbm1lbnRzLgoKKipQbGVhc2UgYWRqdXN0IHRoZSBNQUQudGhyZXNob2xkICh0eXBpY2FsbHkgYmV0d2VlbiAxIGFuZCAzKSBiYXNlZCBvbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHlvdXIgZGF0YXNldCB0byBpZGVudGlmeSB0aGUgb3V0bGllcnMgd2l0aCBsb3cgcXVhbGl0eSBhbmQgaGlnaCBtYXBwaW5nIGVycm9yIHNjb3Jlcy4gVGhpcyB3aWxsIGltcHJvdmUgeW91ciBjbGFzc2lmaWNhdGlvbnMgYW5kIGFueSBkb3duc3RyZWFtIGNvbXBvc2l0aW9uIGFuYWx5c2lzKioKCmBgYHtyfQojICMgT3B0aW9uYWwgc3RlcCAtIHJlbW92ZSBvdXRsaWVycyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvcgojIHF1ZXJ5IDwtIHN1YnNldChxdWVyeSwgbWFwcGluZ19lcnJvcl9RQyA9PSAnUGFzcycpCmBgYAoKT3B0aW9uYWxseSwgb3V0bGllciBjZWxscyB3aXRoIGhpZ2ggbWFwcGluZyBlcnJvciBjYW4gYWxzbyBiZSByZW1vdmVkIGF0IHRoaXMgc3RhZ2UuCkZvciBlYXNlIG9mIGludGVncmF0aW5nIHRoZXNlIG1hcHBlZCBhbm5vdGF0aW9ucyB3aXRoIHRoZSByZXN0IG9mIHlvdXIgYW5hbHlzaXMsIHdlIGFsc28gY2hvb3NlIHRvIHNraXAgdGhpcyBzdGVwLiBJZiBzbywgRmluYWwgQ2VsbFR5cGUgYW5kIFBzZXVkb3RpbWUgcHJlZGljdGlvbnMgd2lsbCBiZSBhc3NpZ25lZCBhcyBOQSBmb3IgY2VsbHMgZmFpbGluZyB0aGUgbWFwcGluZyBlcnJvciBRQyB0aHJlc2hvbGQuIAoKCiMjIyBDZWxsIFR5cGUgQXNzaWdubWVudHMKV2Ugd2lsbCBuZXh0IHVzZSBhIEtOTiBjbGFzc2lmaWVyIHRvIGFzc2lnbiBjZWxsIGlkZW50aXR5IGJhc2VkIG9uIHRoZSAzMCBLLU5lYXJlc3QgTmVpZ2hib3VycyBmcm9tIHRoZSByZWZlcmVuY2UgbWFwLgpUaGlzIGxhYmVsIHRyYW5zZmVyIHN0ZXAgd2lsbCB0YWtlIGxvbmdlciwgcG90ZW50aWFsbHkgYXJvdW5kIDEwIG1pbnV0ZXMgZm9yIH4xMCwwMDAgY2VsbHMgCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9CiMgUHJlZGljdCBIZW1hdG9wb2lldGljIENlbGwgVHlwZXMgYnkgS05OIGNsYXNzaWZpY2F0aW9uCnF1ZXJ5IDwtIHByZWRpY3RfQ2VsbFR5cGVzKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICByZWZfb2JqID0gcmVmLCAKICBpbml0aWFsX2xhYmVsID0gJ2luaXRpYWxfQ2VsbFR5cGUnLCAjIGNlbGx0eXBlIGFzc2lnbm1lbnRzIGJlZm9yZSBtYXBwaW5nIFFDCiAgZmluYWxfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlJyAgIyBjZWxsdHlwZSBhc3NpZ25tZW50cyB3aXRoIG1hcCBRQyBmYWlsaW5nIGNlbGxzIGFzc2lnbmVkIGFzIE5BCikgCgpEaW1QbG90KHN1YnNldChxdWVyeSwgbWFwcGluZ19lcnJvcl9RQyA9PSAnUGFzcycpLCByZWR1Y3Rpb24gPSAndW1hcCcsIGdyb3VwLmJ5ID0gYygncHJlZGljdGVkX0NlbGxUeXBlJyksIHJhc3Rlcj1GQUxTRSwgbGFiZWw9VFJVRSwgbGFiZWwuc2l6ZSA9IDQpCmBgYAoKCiMjIyMgUHNldWRvdGltZSBBbm5vdGF0aW9ucwpXZSBjYW4gYWxzbyBhbm5vdGF0ZSBlYWNoIHF1ZXJ5IGNlbGwgYmFzZWQgb24gdGhlaXIgcG9zaXRpb24gYWxvbmcgaGVtYXRvcG9pZXRpYyBwc2V1ZG90aW1lLiAKUXVlcnkgY2VsbHMgd2lsbCBiZSBhc3NpZ25lZCBhIHBzZXVkb3RpbWUgc2NvcmUgYmFzZWQgb24gdGhlIDMwIEstTmVhcmVzdCBOZWlnaGJvdXJzIGZyb20gdGhlIHJlZmVyZW5jZSBtYXAuClNpbmNlIG91ciBQc2V1ZG90aW1lIEtOTiBhc3NpZ25tZW50cyBhcmUgcGVyZm9ybWVkIGluIFVNQVAgc3BhY2UgKG1vcmUgYWNjdXJhdGUgdGhhbiBLTk4gb24gaGFybW9ueSBjb21wb25lbnRzKSwgdGhpcyBzdGVwIGlzIHZlcnkgZmFzdCAoPCAxMHMpCgpgYGB7cn0KIyBQcmVkaWN0IFBzZXVkb3RpbWUgdmFsdWVzIGJ5IEtOTgpxdWVyeSA8LSBwcmVkaWN0X1BzZXVkb3RpbWUoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIHJlZl9vYmogPSByZWYsIAogIGluaXRpYWxfbGFiZWwgPSAnaW5pdGlhbF9Qc2V1ZG90aW1lJywgCiAgZmluYWxfbGFiZWwgPSAncHJlZGljdGVkX1BzZXVkb3RpbWUnCikKCiMgVmlzdWFsaXplIEhlbWF0b3BvaWV0aWMgUHNldWRvdGltZSBpbiBxdWVyeSBkYXRhCkZlYXR1cmVQbG90KHN1YnNldChxdWVyeSwgbWFwcGluZ19lcnJvcl9RQyA9PSAnUGFzcycpLCBmZWF0dXJlcyA9IGMoJ3ByZWRpY3RlZF9Qc2V1ZG90aW1lJykpCmBgYAoKCiMjIyBTYXZlIHByb2plY3Rpb24gcmVzdWx0cwoKVGhpcyB3aWxsIHNhdmUgYSBjc3YgZmlsZSB3aXRoIHRoZSBtYXBwZWQgYW5ub3RhdGlvbnMgZm9yIGVhY2ggY2VsbCAobWFwcGluZyBlcnJvciBzY29yZXMsIHVtYXAgY29vcmRpbmF0ZXMsIHByZWRpY3RlZCBDZWxsIFR5cGUsIGFuZCBwcmVkaWN0ZWQgUHNldWRvdGltZSkKCmBgYHtyfQojIFNhdmUgQ2VsbFR5cGUgQW5ub3RhdGlvbnMgYW5kIFByb2plY3RlZCBVTUFQIGNvb3JkaW5hdGVzCnNhdmVfUHJvamVjdGlvblJlc3VsdHMoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGNlbGx0eXBlX2xhYmVsID0gJ3ByZWRpY3RlZF9DZWxsVHlwZScsIAogIGNlbGx0eXBlX0tOTnByb2JfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlX3Byb2InLCAKICBwc2V1ZG90aW1lX2xhYmVsID0gJ3ByZWRpY3RlZF9Qc2V1ZG90aW1lJywgCiAgZmlsZV9uYW1lID0gJ3F1ZXJ5ZGF0YV9wcm9qZWN0ZWRfbGFiZWxlZC5jc3YnKQpgYGAKCgojIyMgVmlzdWFsaXplIFByb2plY3Rpb24gRGVuc2l0eQoKTm93IGxldCdzIHZpc3VhbGl6ZSB0aGUgZGVuc2l0eSBkaXN0cmlidXRpb24gb2YgcXVlcnkgY2VsbHMgYWNyb3NzIHRoZSBoZW1hdG9wb2lldGljIGhpZXJhcmNoeQoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTl9CiMgU2V0IGJhdGNoL2NvbmRpdGlvbiB0byBiZSB2aXN1YWxpemVkIGluZGl2aWR1YWxseQpiYXRjaF9rZXkgPC0gJ3NhbXBsZUlEJwoKIyByZXR1cm5zIGEgbGlzdCBvZiBwbG90cyBmb3IgZWFjaCBkb25vciBmcm9tIGEgcHJlLXNwZWNpZmllZCBiYXRjaCB2YXJpYWJsZQpwcm9qZWN0aW9uX3Bsb3RzIDwtIHBsb3RfUHJvamVjdGlvbl9ieURvbm9yKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICBiYXRjaF9rZXkgPSBiYXRjaF9rZXksIAogIHJlZl9vYmogPSByZWYsIAogIEhpZXJhcmNoeV9vbmx5ID0gRkFMU0UsICMgV2hldGhlciB0byBleGNsdWRlIFQvTksvUGxhc21hL1N0cm9tYWwgY2VsbHMgCiAgZG93bnNhbXBsZV9yZWZlcmVuY2UgPSBUUlVFLCAKICBkb3duc2FtcGxlX2ZyYWMgPSAwLjI1LCAgICMgZG93bi1zYW1wbGUgcmVmZXJlbmNlIGNlbGxzIHRvIDI1JTsgcmVkdWNlcyBmaWd1cmUgZmlsZSBzaXplCiAgcXVlcnlfcG9pbnRfc2l6ZSA9IDAuMiwgICAjIGFkanVzdCBzaXplIG9mIHF1ZXJ5IGNlbGxzIGJhc2VkIG9uICMgb2YgY2VsbHMKICBzYXZlcGxvdCA9IFRSVUUsIAogIHNhdmVfZm9sZGVyID0gJ3Byb2plY3Rpb25GaWd1cmVzLycKKQoKIyBzaG93IHBsb3RzIHRvZ2V0aGVyIHdpdGggcGF0Y2h3b3JrLiBDYW4gYWxzbyBqdXN0IGNhbGwgInByb2plY3Rpb25fcGxvdHMiIG9iamVjdCB0byBkaXNwbGF5IG9uZS1ieS1vbmUKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHByb2plY3Rpb25fcGxvdHMsIG5jb2wgPSAyKQpgYGAKCldlIGNhbiBhbHNvIHNldCBIaWVyYXJjaHlfb25seSA9IFRSVUUgdG8gcmVtb3ZlIFQvTksvUGxhc21hL1N0cm9tYWwgY2VsbHMgYW5kIGZvY3VzIHNvbGVseSBvbiB0aGUgaGVtYXRvcG9pZXRpYyBoaWVyYXJjaHkuCgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OH0KIyBTZXQgYmF0Y2gvY29uZGl0aW9uIHRvIGJlIHZpc3VhbGl6ZWQgaW5kaXZpZHVhbGx5CmJhdGNoX2tleSA8LSAnc2FtcGxlSUQnCgojIHJldHVybnMgYSBsaXN0IG9mIHBsb3RzIGZvciBlYWNoIGRvbm9yIGZyb20gYSBwcmUtc3BlY2lmaWVkIGJhdGNoIHZhcmlhYmxlCnByb2plY3Rpb25fcGxvdHMgPC0gcGxvdF9Qcm9qZWN0aW9uX2J5RG9ub3IoCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGJhdGNoX2tleSA9IGJhdGNoX2tleSwgCiAgcmVmX29iaiA9IHJlZiwgCiAgSGllcmFyY2h5X29ubHkgPSBUUlVFLCAjIFdoZXRoZXIgdG8gZXhjbHVkZSBUL05LL1BsYXNtYS9TdHJvbWFsIGNlbGxzIAogIGRvd25zYW1wbGVfcmVmZXJlbmNlID0gVFJVRSwgCiAgZG93bnNhbXBsZV9mcmFjID0gMC4yNSwgICAjIGRvd24tc2FtcGxlIHJlZmVyZW5jZSBjZWxscyB0byAyNSU7IHJlZHVjZXMgZmlndXJlIGZpbGUgc2l6ZQogIHF1ZXJ5X3BvaW50X3NpemUgPSAwLjIsICAgIyBhZGp1c3Qgc2l6ZSBvZiBxdWVyeSBjZWxscyBiYXNlZCBvbiAjIG9mIGNlbGxzCiAgc2F2ZXBsb3QgPSBUUlVFLCAKICBzYXZlX2ZvbGRlciA9ICdwcm9qZWN0aW9uRmlndXJlcy8nCikKCiMgc2hvdyBwbG90cyB0b2dldGhlciB3aXRoIHBhdGNod29yay4gQ2FuIGFsc28ganVzdCBjYWxsICJwcm9qZWN0aW9uX3Bsb3RzIiBvYmplY3QgdG8gZGlzcGxheSBvbmUtYnktb25lCnBhdGNod29yazo6d3JhcF9wbG90cyhwcm9qZWN0aW9uX3Bsb3RzLCBuY29sID0gMikKYGBgCgpOb3RlIGhvdyBjZWxsIGNvbXBvc2l0aW9uIGRpZmZlcnMgYmV0d2VlbiBDRDM0cCBzb3J0ZWQgQWR1bHQgQm9uZSBNYXJyb3cgYW5kIEZldGFsIEJvbmUgTWFycm93IHdpdGhpbiB0aGUgUm95IDIwMjEgZGF0YXNldC4gCk5vdGFibHksIEFkdWx0IGJvbmUgbWFycm93IGlzIGVucmljaGVkIGZvciBtb3JlIGVhcmx5IEhTQy9NUFAgYW5kIExNUFAgY2VsbHMgYW5kIGFsc28gTUVQICYgRWFybHkgRXJ5dGhyb2lkIGxpbmVhZ2VzLiAKSW4gY29udHJhc3QsIEZldGFsIGJvbmUgbWFycm93IGlzIGhpZ2hseSBlbnJpY2hlZCBmb3IgTUxQIGFuZCBCIGNlbGwgcHJvZ2VuaXRvcnMuIAoKV2UgY2FuIGFsc28gY29tcGFyZSB0aGUgbWFwcGluZyBvZiB0aGVzZSBzYW1wbGVzIGFsb25nIEhlbWF0b3BvaWV0aWMgcHNldWRvdGltZToKCgojIyMgR2V0IENvbXBvc2l0aW9uIGRhdGEgZm9yIGVhY2ggZG9ub3IKCkhlcmUsIHRvIHN0dWR5IHRoZSBhYnVuZGFuY2Ugb2YgZWFjaCBjZWxsIHR5cGUgd2l0aGluIGVhY2ggZG9ub3IsIEkgZm9jdXMgb24gY2VsbHMgdGhhdCB3ZXJlIGNsYXNzaWZpZWQgd2l0aCBhIEtOTiBwcm9iID4gMC41ICh0aGF0IGlzLCA+NTAlIG9mIG5lYXJlc3QgbmVpZ2hib3VycyBmcm9tIHRoZSByZWZlcmVuY2UgbWFwIGFncmVlIG9uIHRoZSBhc3NpZ25lZCBjZWxsIHR5cGUpLiAKCldlIGNhbiBwcmVzZW50IHRoaXMgYXMgYSBsb25nIHRhYmxlCgpgYGB7cn0KcXVlcnlfY29tcG9zaXRpb24gPC0gZ2V0X0NvbXBvc2l0aW9uKAogIHF1ZXJ5X29iaiA9IHF1ZXJ5LCAKICBkb25vcl9rZXkgPSAnc2FtcGxlSUQnLCAKICBjZWxsdHlwZV9sYWJlbCA9ICdwcmVkaWN0ZWRfQ2VsbFR5cGUnLCAKICBtYXBRQ19jb2wgPSAnbWFwcGluZ19lcnJvcl9RQycsIAogIGtubl9wcm9iX2N1dG9mZiA9IE5VTEwsIAogIHJldHVybl90eXBlID0gJ2xvbmcnKQoKcXVlcnlfY29tcG9zaXRpb24gCmBgYAoKT3IgYXMgYSB3aWRlIHRhYmxlIHdpdGggY291bnRzIG9mICMgb2YgY2VsbHMKCmBgYHtyfQpxdWVyeV9jb21wb3NpdGlvbiA8LSBnZXRfQ29tcG9zaXRpb24oCiAgcXVlcnlfb2JqID0gcXVlcnksIAogIGRvbm9yX2tleSA9ICdzYW1wbGVJRCcsIAogIGNlbGx0eXBlX2xhYmVsID0gJ3ByZWRpY3RlZF9DZWxsVHlwZScsIAogIG1hcFFDX2NvbCA9ICdtYXBwaW5nX2Vycm9yX1FDJywgCiAga25uX3Byb2JfY3V0b2ZmID0gTlVMTCwgCiAgcmV0dXJuX3R5cGUgPSAnY291bnQnKQoKcXVlcnlfY29tcG9zaXRpb24gCmBgYAoKT3IgYXMgYSB3aWRlIHRhYmxlIHdpdGggcHJvcG9ydGlvbiBvZiBlYWNoIGNlbGwgdHlwZSB3aXRoaW4gZWFjaCBkb25vcgoKYGBge3J9CnF1ZXJ5X2NvbXBvc2l0aW9uIDwtIGdldF9Db21wb3NpdGlvbigKICBxdWVyeV9vYmogPSBxdWVyeSwgCiAgZG9ub3Jfa2V5ID0gJ3NhbXBsZUlEJywgCiAgY2VsbHR5cGVfbGFiZWwgPSAncHJlZGljdGVkX0NlbGxUeXBlJywgCiAgbWFwUUNfY29sID0gJ21hcHBpbmdfZXJyb3JfUUMnLCAKICBrbm5fcHJvYl9jdXRvZmYgPSBOVUxMLCAKICByZXR1cm5fdHlwZSA9ICdwcm9wb3J0aW9uJykKCnF1ZXJ5X2NvbXBvc2l0aW9uIApgYGAKCgpgYGB7cn0KIyBTaW1wbGUgaGVhdG1hcCB0byB2aXN1YWxpemUgY29tcG9zaXRpb24gb2YgcHJvamVjdGVkIHNhbXBsZXMKcCA8LSBxdWVyeV9jb21wb3NpdGlvbiAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCdzYW1wbGVJRCcpICU+JSBkYXRhLm1hdHJpeCgpICU+JSBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCgpCnAKYGBgCg==